//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentModel
{
    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using ER = Microsoft.Research.DataLayer;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Windows;
    #endregion 

    /// <summary>
    /// Describes a subsection in the workflow.
    /// </summary>
    [Serializable()]
    public class TridentSubsectionModel : BaseCompositeModel
    {
        #region Private variables

        /// <summary>
        /// Whether Collapsed.
        /// </summary>
        private bool isCollapsed;

        private ParameterDescriptionModel labelParameter;

        private ParameterDescriptionModel descriptionParameter;

        private ParameterDescriptionModel collapsedParameter;

        /// <summary>
        /// Visibility of the activity and it's parameters.
        /// </summary>
        private ActivityModelVisibility visibleOnUi = new ActivityModelVisibility(Visibility.Visible, Visibility.Collapsed);

        #endregion

        #region Constructors

        /// <summary>
        /// Creates an empty subsection.
        /// </summary>
        /// <param name="parentActivityModel">
        /// The activity model of the subsection to be created.
        /// </param>
        /// <param name="indexOfSubsection">
        /// The index of the subsection.
        /// </param>
        /// <param name="nameOfSubsection">
        /// Name of the subsection.
        /// </param>
        /// <param name="desc">
        /// Description.
        /// </param>        
        public TridentSubsectionModel(ActivityMetadata source, BaseCompositeModel parentModel, string label, string description, string uniqueCount)
        {
            // Set the input and output parameters.
            foreach (ParameterDescriptionModel metadata in source.InputParameters)
            {
                this.InputParameters.Add(new ParameterDescriptionModel(metadata));
            }

            this.labelParameter = this.InputParameters.FirstOrDefault(value => value.Name == "Label");
            this.descriptionParameter = this.InputParameters.FirstOrDefault(value => value.Name == "Description");
            this.collapsedParameter = this.InputParameters.FirstOrDefault(value => value.Name == "IsCollapsed");

            this.ParentModel = parentModel;

            this.Label = label;

            this.Description = description;

            //  Set the Registry ID of the Activity.
            this.Id = source.Id;

            //// Set the UniqueID.
            this.UniqueId = source.Name + "_" + uniqueCount;

            // Set Class of the activity.
            this.ActivityClass = source.ActivityClass;

            // Set Name of the activity.
            this.Name = source.Name;

            this.Children.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnChildCollectionChanged);
            this.ChildrenCollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(this.OnChildCollectionChanged);
        }
        
        /// <summary>
        ///  Constructor of Activity Model.
        /// </summary>
        /// <param name="erActivitySequence">Activity for which the model has to be created.</param>
        /// <param name="parentModel">Parent model of teh Activity Sequence.</param>
        public TridentSubsectionModel(ER.ActivitySequence erActivitySequence, BaseCompositeModel parentModel, ModelFactory modelFactory)
        {
            #region Validation of Arguments
            if (null == erActivitySequence)
            {
                throw new ArgumentNullException("erActivitySequence");
            }
            #endregion

            // Create the Activity Model.
            this.CreateActivityModel(
                erActivitySequence,
                parentModel,
                modelFactory,
                erActivitySequence.Name);

            this.UpdateParameterAssignments(erActivitySequence);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositeActivityModel"/> class.
        /// </summary>
        /// <param name="updatedActivity">The updated activity.</param>
        /// <param name="parentModel">The parent model.</param>
        /// <param name="uniqueCount">The unique count.</param>
        public TridentSubsectionModel(ER.Activity updatedActivity, BaseCompositeModel parentModel, string uniqueCount)
        {
            #region Validation of Arguments
            if (null == updatedActivity)
            {
                throw new ArgumentNullException("updatedActivity");
            }
            #endregion

            // Create the Activity Model.
            this.CreateActivityModel(
                updatedActivity,
                parentModel,
                updatedActivity.Name + "_" + uniqueCount);
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Gets whether the Activity is of Built In Type.
        /// </summary>
        public override bool IsCollapsed
        {
            get
            {
                return bool.Parse(this.collapsedParameter.Value.ToString()); 
            }
            set
            {
                if ((bool)value != bool.Parse(this.collapsedParameter.Value.ToString()))
                {
                    this.isCollapsed = value;                    
                    this.collapsedParameter.Value = value;
                    if (this.SwapSubsectionState != null)
                    {
                        this.SwapSubsectionState.Invoke(this, new EventArgs());
                    }                    
                }
            }
        }

        /// <summary>
        /// Gets and sets the value of Label
        /// </summary>
        public override string Label
        {
            get
            {
                return (string)labelParameter.Value;
            }
            set
            {
                string assignedVal = value;
                if (!string.IsNullOrEmpty(assignedVal) && !string.IsNullOrEmpty(assignedVal.Trim()))
                {
                    labelParameter.Value = assignedVal.Trim();
                }
            }
        }

        /// <summary>
        /// Gets and sets the value of Description
        /// </summary>
        public override string Description
        {
            get
            {
                return (string)descriptionParameter.Value;
            }
            set
            {
                descriptionParameter.Value = value;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a simple activity model.
        /// </summary>
        public override bool IsSimpleActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a composite activity model.
        /// </summary> 
        public override bool IsCompositeActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a subsection model.
        /// </summary>
        public override bool IsSubsectionModel
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a For model.
        /// </summary>
        public override bool IsForActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a replicator model.
        /// </summary>
        public override bool IsReplicatorActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is an ifElse model.
        /// </summary>
        public override bool IsIfElseActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override bool IsSimpleBlackBoxModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override bool IsCompositeBlackBoxModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// The parameter binding visibility.
        /// </summary>
        public override Visibility CurrentParameterBindingVisibility
        {
            get
            {
                return Visibility.Visible;
            }
            set
            {
                
            }
        }

        /// <summary>
        /// Gets or sets the representation of the activity on the property pane.
        /// </summary>
        public override ActivityModelVisibility VisibleOnUI
        {
            get
            {
                return this.visibleOnUi;
            }

            set
            {
                this.visibleOnUi = value;
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Fired when the subsection has to go from expanded to collapsed and vice-versa.
        /// </summary>
        [field: NonSerialized]
        public event EventHandler SwapSubsectionState;

        #endregion Events

        #region Public Methods

        /// <summary>
        /// Using the BaseVisitor, perform the operation.
        /// </summary>
        /// <param name="visitor">
        /// The implementation of visitor.
        /// </param>
        public override void Accept(TridentBaseVisitor visitor)
        {
            visitor.VisitSubsectionModel(this);
        }

        #endregion Public Methods

        /// <summary>
        /// This function is used to create the Activity Model.
        /// </summary>
        /// <param name="erActivitySequence">Activity Sequence of the Activity for which the Activity Model has to be created.</param>
        /// <param name="parentActivity">Parent Activity Model.</param>
        /// <param name="uniqueName">Unique ID of the Activity.</param>
        /// <returns>Activty Model.</returns>
        private TridentSubsectionModel CreateActivityModel(
            ER.ActivitySequence erActivitySequence,
            BaseCompositeModel parentActivity,
            ModelFactory modelFactory,
            string uniqueName)
        {
            ER.Activity erActivity = erActivitySequence.Activity;
            this.CreateActivityModel(erActivity, parentActivity, uniqueName);

            for (int index = 0; index < erActivitySequence.Children.Count; index++)
            {
                ER.ActivitySequence childActivity = erActivitySequence.Children[index];
                if (childActivity != null)
                {
                    BaseModel childModel = modelFactory.FetchModelByActivitySequence(childActivity, this);
                    if (null != childModel)
                    {
                        // Add childActivity Model to the parent.
                        this.Children.Add(childModel);
                    }
                }
            }

            return this;
        }
        
        /// <summary>
        /// Creates the activity model.
        /// </summary>
        /// <param name="erActivity">The er activity.</param>
        /// <param name="parentActivity">The parent activity.</param>
        /// <param name="uniqueName">Name of the unique.</param>
        /// <returns></returns>
        private TridentSubsectionModel CreateActivityModel(
            ER.Activity erActivity,
            BaseCompositeModel parentActivity,
            string uniqueName)
        {
            // Set the Registry ID of the Activity.
            this.Id = ((ER.IObject)erActivity).ID;

            // Set the UniqueID.
            this.UniqueId = uniqueName;

            // Set Class of the activity.
            this.ActivityClass = erActivity.ActivityClass;

            // Set Name of the activity.
            this.Name = erActivity.Name;

            // Set whether the activity is Deleted.
            this.IsDeleted = erActivity.IsDeleted;

            // Check if an update is available.
            ER.Activity latestActivity = BaseModel.CheckIfUpdateAvailable(erActivity);
            if (latestActivity != null)
            {
                this.UpdateAvailable = true;
                this.LatestEntityId = (latestActivity as ER.IObject).ID;
            }

            this.Version = erActivity.Version;

            // Set the Parent of this activity.
            if (null != parentActivity)
            {
                this.ParentModel = parentActivity;
            }

            this.Children.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnChildCollectionChanged);
            this.ChildrenCollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(this.OnChildCollectionChanged);

            // Initialize Parameters.
            this.InitParamModels(erActivity);
            return this;
        }

        /// <summary>
        /// This is used to initialize the Paramaeter description Model.
        /// </summary>
        /// <param name="erActivity">ActivitySequence for which the model has to be created.</param>
        private void InitParamModels(ER.Activity erActivity)
        {
            // Get and set all the paramModel in the Activity.
            foreach (ER.ActivityParameter param in erActivity.ActivityParameters)
            {
                ParameterDescriptionModel paramModel = new ParameterDescriptionModel(param);

                if (param.Direction == ER.DirectionType.Input)
                {
                    // Input Parameter.
                    this.InputParameters.Add(paramModel);
                }
                else
                {
                    // Output Parameter.
                    this.OutputParameters.Add(paramModel);
                }
            }

            this.labelParameter = this.InputParameters.FirstOrDefault(value => value.Name == "Label");
            this.descriptionParameter = this.InputParameters.FirstOrDefault(value => value.Name == "Description");
            this.collapsedParameter = this.InputParameters.FirstOrDefault(value => value.Name == "IsCollapsed");
        }

        /// <summary>
        /// Update the parameter assignments for the activity model.
        /// </summary>
        /// <param name="activitySequence"></param>
        public void UpdateParameterAssignments(ER.ActivitySequence activitySequence)
        {
            // Iterate through the parameter assignments of the activity sequence.
            foreach (ER.ParameterAssignment parameterAssignment in activitySequence.ParameterAssignments)
            {
                string parameterName = parameterAssignment.ActivityParameter.Name;

                // Retreive the target parameter.
                ParameterDescriptionModel paramModel = this.GetParameterDescriptionModel(parameterName);
                if (paramModel != null)
                {
                    // If the parameter is databound then set the bindings.
                    if (parameterAssignment.IsBound)
                    {
                        paramModel.UpdateBindings(parameterAssignment);
                    }
                    else
                    {
                        // else update the value.
                        paramModel.UpdateValue(parameterAssignment);
                    }
                }
            }
        }

        /// <summary>
        /// Return the parameter model based on the parameter name.
        /// </summary>
        /// <param name="parameterName">The unique name.</param>
        /// <returns>The parameter model.</returns>
        private ParameterDescriptionModel GetParameterDescriptionModel(string parameterName)
        {
            // Check in the input parameters.
            foreach (ParameterDescriptionModel inputParam in this.InputParameters)
            {
                if (inputParam.PropertyName.Equals(parameterName, StringComparison.CurrentCultureIgnoreCase))
                {
                    return inputParam;
                }
            }

            // Check in the output parameters.
            foreach (ParameterDescriptionModel outputParam in this.OutputParameters)
            {
                if (outputParam.PropertyName.Equals(parameterName, StringComparison.CurrentCultureIgnoreCase))
                {
                    return outputParam;
                }
            }

            // Return null if parameter model not found.
            return null;
        }
    }
}
